package ec_si_template;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import cec2010suite.Function;

/**
 * This class implements the individuals (i.e. particles, chromosomes, fish,
 * etc.)
 * 
 * @author Andreas Janecek
 * 
 */
public class Individual {

	/**
	 * the current location of the individual
	 */
	private double[] p;

	/**
	 * the current fitness of the individual
	 */
	private double fitness;

	/**
	 * the object of the function to be optimized
	 */
	private Function func;

	/**
	 * the index of the individual
	 */
	private int index;

	/**
	 * the luciferin value, the algorithm's solution evaluation
	 */
	private double luciferinLevel;

	/**
	 * Radius of the decision Range
	 */
	private double neighbourhoodRange;

	/**
	 * The set of neighbours within the decision range
	 */
	private List<Individual> localNeighbours;

	/**
	 * The set of all neighbours
	 */
	private Individual[] Neighbours;

	/**
	 * The probability of moving towards a neighbor
	 */
	private double[] probabilityOfMovingTowardNeighbour;

	/**
	 * Constructor: Initialize the individual. Find random position within
	 * initialization range and compute fitness of this position
	 * 
	 * @param func
	 *            : the function object
	 * @param dim
	 *            : the problem dimension
	 * @param minInit
	 *            : lower limit of the initialization range
	 * @param maxInit
	 *            : upper limit of the initialization range
	 * @param minBound
	 *            : lower limit of the search space
	 * @param maxBound
	 *            : upper limit of the search space
	 */
	public Individual(int index, Function func, int dim, double minInit,
			double maxInit, double minBound, double maxBound) {
		this.index = index;
		this.func = func;
		this.p = new double[dim];

		this.localNeighbours = new ArrayList<Individual>();

		// initialize the default values
		this.luciferinLevel = Config.INITIAL_LUCIFERIN;
		
		this.neighbourhoodRange = Config.MAX_VISION_RANGE;
		
		// Initialize the swarm / the population at a new position
		for (int i = 0; i < dim; i++) {
			p[i] = (minInit + (new Random().nextDouble() * (maxInit - minInit)));
		}

		// calculate fitness
		this.fitness = this.func.compute(this.p);
	}

	/**
	 * return the current position
	 * 
	 * @return the current position
	 */
	public double[] getp() {
		return this.p;
	}

	/**
	 * return the current fitness
	 * 
	 * @return the current fitness
	 */
	public double getfitness() {
		return this.fitness;
	}

	/**
	 * return index
	 * 
	 * @return index
	 */
	public int getindex() {
		return this.index;
	}

	/**
	 * update the individual (this is algorithm specific) EXAMPLE: here, a
	 * simple random hill climber is used. If new fitness is better than the old
	 * one, p and fitness are updated
	 * 
	 * @param dim
	 *            : the problem dimension
	 * @param minBound
	 *            : lower limit of the search space
	 * @param maxBound
	 *            : upper limit of the search space
	 */
	public void update(int dim, double minBound, double maxBound) {

		locateLocalNeighbours();
		CalculateProbabilityToMoveTowardNeighbour();
		Individual selectedNeighbour = SelectNeighbour();
		MoveToward(selectedNeighbour);
		UpdateVisionRange();
	}

	/**
	 * As the luciferin value has to be evaluated in a diferent step for the
	 * individuals, so there is a separeted method for it
	 */
	public void UpdateLuciferin() {
		this.fitness = this.func.compute(this.p);

		double luciferin = (1 - Config.LUCIFERIN_DECAY) * this.luciferinLevel
				+ Config.LUCIFERIN_ENHANCEMENT * this.fitness;

		this.luciferinLevel = Math.max(0, luciferin);
	}

	public void setNeighbours(Individual[] GlowWormsInSwarm) {

		this.Neighbours = GlowWormsInSwarm;

	}

	public void locateLocalNeighbours() {

		this.localNeighbours.clear();

		for (Individual other : this.Neighbours) {

			if (other != this) {
				if (this.IsNeighbour(other)) {
					this.localNeighbours.add(other);
				}
			}

		}
	}

	private boolean IsNeighbour(Individual GlowWorm) {
		if (GlowWorm.luciferinLevel < this.luciferinLevel) {
			return (norm(GlowWorm.p, this.p) <= this.neighbourhoodRange);
		} else {
			return false;
		}
	}

	public void CalculateProbabilityToMoveTowardNeighbour() {
		this.probabilityOfMovingTowardNeighbour = new double[this.localNeighbours
				.size()];
		double[] probabilities = new double[this.localNeighbours.size()];
		double sum = 0.0;

		for (int i = 0; i < this.localNeighbours.size(); i++) {
			double luciferinDifference = this.localNeighbours.get(i).luciferinLevel
					- this.luciferinLevel;
			probabilities[i] = luciferinDifference;
			sum += luciferinDifference;
		}

		for (int i = 0; i < probabilities.length; i++) {
			this.probabilityOfMovingTowardNeighbour[i] = probabilities[i] / sum;
		}
	}

	public Individual SelectNeighbour() {
		if (this.localNeighbours.size() <= 0) {
			return this;
		} else {
			Random randomGenerator = new Random();

			int selected = randomGenerator
					.nextInt(probabilityOfMovingTowardNeighbour.length);

			return this.localNeighbours.get(selected);
		}
	}

	public void MoveToward(Individual neighbour) {
		double[] difference = SubtractVectors(neighbour.p, this.p);
		double norm = norm(neighbour.p, this.p);

		if (norm > 0.0) {
			for (int i = 0; i < this.p.length; i++) {
				double oldposition = this.p[i];
				this.p[i] = oldposition + Config.STEP_SIZE
						* (difference[i] / norm);
			}
		}
	}

	public void UpdateVisionRange() {
		double diference = Config.NEIGHBOURHOOD_CHANGE_RATE
				* (Config.NEIGHBOURHOOD_THRESHOLD - this.localNeighbours.size());
		
		double maxNeighboursDifference = Math.max(0.0, this.neighbourhoodRange
				+ diference);

		this.neighbourhoodRange = Math.min(Config.MAX_VISION_RANGE,
				maxNeighboursDifference);
	}

	/*
	 * auxiliar methods to calculate the distance from the neighbors
	 */

	public static double[] SubtractVectors(double[] firstVector,
			double[] secondVector) {
		double[] result = new double[firstVector.length];

		for (int i = 0; i < firstVector.length; i++) {
			result[i] = firstVector[i] - secondVector[i];
		}

		return result;
	}

	public static double SquaredSum(double[] vector) {
		double result = 0.0;

		for (int i = 0; i < vector.length; i++) {
			result += Math.pow(vector[i], 2);
		}

		return result;
	}

	public static double norm(double[] firstVector, double[] secondVector) {
		double[] subtractedVector = SubtractVectors(firstVector, secondVector);
		double sum = SquaredSum(subtractedVector);
		double norm = Math.sqrt(sum);

		return norm;
	}

}
